/*  GPIO
GPIO1
GPIO2
GPIO4
GPIO5
GPIO6
GPIO7
GPIO8
GPIO9
GPIO10
GPIO11
GPIO12
GPIO13
GPIO14
GPIO15
GPIO16
GPIO17
GPIO18
GPIO21
GPIO35
GPIO36
GPIO37
GPIO38
GPIO39
GPIO40
GPIO41
GPIO42
GPIO45
GPIO46
GPIO47
*/
#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <Update.h>
#include <ESPmDNS.h>
#include <ArduinoJson.h>
#include <vector>
#include <SPIFFS.h>
#include <mutex>

std::mutex mtx;  // 全局互斥锁

#define BOARD_NAME "ESP32S3"// 开发板配置
std::vector<u8_t> init_gpios_high = {1,2,41,42};
std::vector<u8_t> init_gpios_low = {};

const char* www_username = "admin";// 用户认证信息
const char* www_password = "esp32password";
const char* ssid = "adol-3447C6";// WiFi配置
const char* password = "12345678";

AsyncWebServer server(80);// Web服务器配置
const char* host = "ESP32S3";
u8_t htttp_request_count = 0;

#define QUEUE_STATUS_WRITABLE  			0 //队员状态为可写，由USART（串口）写结束后改为 QUEUE_STATUS_READABLE （可读）。
#define QUEUE_STATUS_READABLE  			1 //队员状态为可读，由man或其他函数读取并处理结束后改为 QUEUE_STATUS_WRITABLE （可写）。
#define Q_LEN  			20//队列长度
#define U_LEN  			2048//队员长度
struct ops_queue {
  char c[U_LEN];
  bool s;
};
ops_queue ops_queueb[Q_LEN] = {};
typedef void (*func_ptr)(char* str);// 定义函数指针类型
typedef struct {//动态回调函数（高级用法），通过函数指针和全局结构体实现动态逻辑绑定
	void (*callback)(char *);
	char *param;
} dynamic_callback;
u8_t q_readable_count = 0;//可读队员数
u8_t q_search_readable_i = 0;//用于寻找可读队员，找到就进入处理状态
u8_t q_search_writable_i = 0;//用于寻找可读队员，找到就进入写状态
func_ptr fp = NULL;
unsigned int num_fp;
char *endptr_fp;//用于strtol函数调用后检查错误 if (endptr_fp == queue_a[queue_handling_i].c) {// 无有效数字//} else if (*endptr != '\0') {// 字符串含非数字字符（例如"abc"）//}
char *temp,*next_p,*temp_next_p;
void delayMs(char *xb){
	char *endptr_str;
	unsigned int delay_time = strtol(xb, &endptr_str, 10);
	unsigned int times = delay_time/1000;
	unsigned int i=0;
	for(i=0;i<times;i++){
		delay(1000);
	}
	printf("delayMs|%d\r\n",delay_time);
}
char *myStrsep(char *curr_p, const char *delim,char **next_p) {
	char *end = curr_p + strcspn(curr_p, delim); 
	//printf("myStrsep %s\r\n",curr_p);
	if (*curr_p == '\0') {return *next_p = NULL;}
	*end = '\0';
	*next_p = end + 1;
	return curr_p;
}
// 初始化文件系统
void initSPIFFS() {
  if (!SPIFFS.begin(true)) {Serial.println("Unable to mount SPIFFS file system");return;}
  Serial.println("SPIFFS file system mounted");
}
JsonDocument readData(char* fl){// 加载数据
  JsonDocument doc;
  File file = SPIFFS.open(fl, FILE_READ);
  if (!file) {Serial.println("Failed to open file for writing"); doc["b"] = "x";return doc;}
  //File configFile = SPIFFS.open("/config.json", "r");
  DeserializationError error = deserializeJson(doc, file);
  file.close();
  if (error) {Serial.print("JSON解析错误: ");Serial.println(error.c_str());doc["b"] = "x";return doc;}
  return doc;
}
void initGPIO(const std::vector<u8_t>& gpios, bool level) {
    for (auto pin : gpios) {
        pinMode(pin, OUTPUT);
        digitalWrite(pin, level);
    }
}
void gpioOutLowHigh(char *pin_level){
  char *endptr_str;
	u8_t pin = strtol(myStrsep(pin_level, "|",&pin_level), &endptr_str, 10);
  pinMode(pin, OUTPUT);
  digitalWrite(pin, strtol(pin_level, &endptr_str, 10));
}
// 初始化WiFi连接
void initWiFi() {
  Serial.println("\nConnecting to WiFi...");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  WiFi.setHostname(host);// 设置主机名
  uint8_t dots = 0;
  while (WiFi.status() != WL_CONNECTED) {// 显示连接进度
    delay(500);
    Serial.print(".");
    if (++dots > 40) {
      Serial.println("\nConnection timeout, try reconnecting...");
      WiFi.reconnect();
      dots = 0;
    }
  }
  Serial.println("\nWiFi is connected!");
  Serial.print("IP address:");
  Serial.println(WiFi.localIP().toString());
  Serial.print("Signal strength:");
  Serial.print(map(WiFi.RSSI(), -100, -40, 0, 100));
  Serial.println("");
}
// 初始化mDNS服务
void initMDNS() {
  if (!MDNS.begin(host)) {
    Serial.println("MDNS startup failed!");
    return;
  }
  MDNS.addService("http", "tcp", 80);
  Serial.print("MDNS service started:http://");
  Serial.print(host);
  Serial.println(".local");
}
// 未找到页面处理
void handleNotFound(AsyncWebServerRequest *request) {
  String message = "文件未找到\n\n";
  message += "URI: ";
  message += request->url();
  message += "\n方法: ";
  message += (request->method() == HTTP_GET) ? "GET" : "POST";
  request->send(404, "text/plain", message);
}
// 初始化Web服务器
void initWebServer() {
  server.on("/", HTTP_GET,[](AsyncWebServerRequest *request) {// 基本页面路由
      htttp_request_count++;
      String html = R"=====(
      <!DOCTYPE html>
      <html lang="zh-CN">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>ESP32-S3</title>
        <style>
          #customAlert {/* 弹窗样式 - 可自定义 */
            display: none;position: fixed;top: 20px;left: 50%;transform: translateX(-50%);background: #4CAF50;color: white;padding: 15px 30px;border-radius: 5px;box-shadow: 0 4px 8px rgba(0,0,0,0.2);font-family: Arial, sans-serif;z-index: 1000;animation: fadeIn 0.3s;
          }
        @keyframes fadeIn {/* 关闭动画效果 */
            from {opacity: 0; top: 0;}
            to {opacity: 1; top: 20px;}
          }
        </style>
      </head>
      <body>
        <p>
          <input type="text" placeholder="wifi 名">
          <input type="text" placeholder="wifi 密码">
          <button onclick="submitb(this,'wifiInfo')" type="button">修改</button>         
          <button onclick="getData('wifiInfo')" type="button">获取</button>
        </p>
        <p>
          <input type="text" placeholder="ops">
          <button onclick="submitb(this,'opcombinations')" type="button">发送</button></p>
        <button onclick="controlLED('toggle')">切换LED</button>
        <button onclick="location.href='/update'">固件更新</button>
        <div id="customAlert"></div>
          <script>
            function showAlert(msg, tim = 3000, bgColor = '#4CAF50') {
              const alertBox = document.getElementById('customAlert');
              alertBox.textContent = msg;// 设置内容和样式
              alertBox.style.backgroundColor = bgColor;
              alertBox.style.display = 'block';// 显示弹窗
              if(alertBox.timeoutId) clearTimeout(alertBox.timeoutId);// 清除之前的定时器（避免多次点击冲突）
              alertBox.timeoutId = setTimeout(() => {// 设置新的定时器
                alertBox.style.display = 'none';
              }, tim);
            }           
            async function submitb(ths, info) {
              const parent = ths.parentNode;
              var dt ="";
              const inputs = parent.querySelectorAll('input');              
              Array.from(inputs).map(input => {
                dt += input.value + "|";//encodeURIComponent()
              }).filter(val => val !== null);
              dt=dt.slice(0, -1);
              console.log(dt);
              try {
                const response = await fetch('/'+info, {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/x-www-form-urlencoded',
                  'Authorization': 'Basic ' + btoa('admin:esp32password')
                },
                body: `${info}=${encodeURIComponent(dt)}`
              });
              const text = await response.text();
              //const errorResult = await response.json();//errorResult.msg${errorResult.wf_id}
              showAlert(`${response.status}: ${response.statusText}:${text}`, 3000, "#f44336");
              } catch (error) {
                showAlert(error, 3000, "#f44336");
              }
            }
            async function getData(fn) {
              try {
                const response = await fetch('/readData', {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/x-www-form-urlencoded',
                  'Authorization': 'Basic ' + btoa('admin:esp32password')
                },
                body: `fn=${encodeURIComponent(fn)}`
              });
              const text = await response.text();
              //const errorResult = await response.json();
              showAlert(`${response.status}: ${response.statusText}:${text}`, 3000, "#f44336");
              } catch (error) {
                showAlert(error, 3000, "#f44336");
              }
            }
            function controlLED(action) {
              fetch('/led/' + action)
                .then(response => response.text())
                .then(data => {
                  showAlert(data,3000,"#4CAF50");
                  //location.reload();
                });
            }
          </script>
      </body>
      </html>
      )=====";
      request->send(200, "text/html", html);
  });
  server.on("/opcombinations", HTTP_POST, [](AsyncWebServerRequest *request) {
    if(Q_LEN==q_readable_count){request->send(401, "text/plain", "queue_full");return;}
    const AsyncWebParameter *p = request->getParam(0);
    String valueStr = p->value();
    while(1){//寻找可写队员（queue_a[q_search_writable_i].s为QUEUE_STATUS_WRITABLE
      if(Q_LEN == q_search_writable_i)q_search_writable_i = 0;
			if(QUEUE_STATUS_WRITABLE == ops_queueb[q_search_writable_i].s)break;
			q_search_writable_i++;
		}
    valueStr.toCharArray(ops_queueb[q_search_writable_i].c, sizeof(ops_queueb[q_search_writable_i].c));
    ops_queueb[q_search_writable_i].s = QUEUE_STATUS_READABLE;
    q_search_writable_i++;
    q_readable_count++;
    request->send(200, "text/plain",  "xb");
  });
  server.on("/writeData", HTTP_POST, [](AsyncWebServerRequest *request) {//检索存储的数据
    const AsyncWebParameter *p = request->getParam(0);
    File file = SPIFFS.open("/" + p->name(), FILE_WRITE);
    if (!file) {Serial.println("Failed to open file for writing");request->send(401, "application/json", p->value());}
    file.print(p->value());
    Serial.println(p->value());
    file.close();
    request->send(200, "application/json", p->value());
  });
  server.on("/readData", HTTP_POST, [](AsyncWebServerRequest *request) {//检索存储的数据
    //if(!request->authenticate(www_username, www_password)) {
    //  return request->requestAuthentication();
    //}
    const AsyncWebParameter *p = request->getParam(0);
    if (!SPIFFS.exists("/" + p->value())) {Serial.println("file does not exist");request->send(401, "application/json", "file does not exist");}
    File file = SPIFFS.open("/" + p->value(), "r");
    if (!file) {Serial.println("Unable to open file for reading");request->send(402, "application/json", "Unable to open file for reading");}
    String str = file.readString();
    Serial.println(str);
    file.close();
    request->send(200, "application/json", str);
  });
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("The HTTP server has been started");// 未找到路由处理
}


// 系统初始化
void setup() {
  initGPIO(init_gpios_high,1);
  initGPIO(init_gpios_low,0);
  Serial.begin(115200);
  initSPIFFS();
  // 打印启动信息
  Serial.printf("xxxxxxxxxxxxxbbbbbbb%dxx%d",gpioOutLowHigh,delayMs);
  Serial.println("\n\n=== ESP32-S3 Web Service startup ===");
  Serial.printf("Chip model: %s\n", ESP.getChipModel());
  Serial.printf("Number of chip cores: %d\n", ESP.getChipCores());
  Serial.printf("SDK version: %s\n", ESP.getSdkVersion());
  Serial.printf("Flash size: %dMB\n", ESP.getFlashChipSize() / (1024 * 1024));
  initWiFi();// 初始化WiFi
  initMDNS();// 初始化mDNS
  initWebServer();// 初始化Web服务器
  // 闪烁LED表示启动完成
  for (int i = 0; i < 3; i++) {
    digitalWrite(1, LOW);
    delay(1000);
    digitalWrite(1, HIGH);
    delay(1000);
  }
}

// 主循环
void loop() {
//  static unsigned long lastUpdate = 0;
//  if (millis() - lastUpdate >= 10000) {// 每10秒更新系统状态
//    lastUpdate = millis();
    // 打印系统状态
//    Serial.printf("[system state] Memory: %dKB | WiFi: %d%% | request: %d", ESP.getFreeHeap() / 1024,map(WiFi.RSSI(), -100, -40, 0, 100),htttp_request_count);
//    Serial.println("");
//  } 
//  delay(10);// 短暂延迟
  while(q_readable_count){//寻找可读队员（ops_queueb[q_search_readable_i].s为QUEUE_STATUS_READABLE
    if(Q_LEN == q_search_readable_i)q_search_readable_i = 0;//最后队号不可读但可读队员数不为0时，从头开始寻找可读队员，如此往复
    if(QUEUE_STATUS_WRITABLE == ops_queueb[q_search_readable_i].s){q_search_readable_i++;continue;}//跳过可写队员
      Serial.printf("q_readable_count %d Start processing ops_queueb[%d] %s\r\n",q_readable_count,q_search_readable_i,ops_queueb[q_search_readable_i].c);
      next_p = ops_queueb[q_search_readable_i].c;
      while(*next_p){
        temp = myStrsep(next_p, "_",&next_p);
        num_fp = strtol(myStrsep(temp,"|",&temp_next_p), &endptr_fp, 10);
        fp = (func_ptr)num_fp;
        fp(temp_next_p);// 执行函数
      }
      //printf("ops_queueb[%d] processing completed",q_search_readable_i);
      ops_queueb[q_search_readable_i].s = QUEUE_STATUS_WRITABLE;
      q_search_readable_i++;
      q_readable_count--;
	}
}